package cn.itcast.nems.payment.service.impl;

import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.ic.common.exception.v2.bean.BizStatusEnum;
import cn.itcast.nems.common.util.LocalDateTimeUtils;
import cn.itcast.nems.manager.payment.PaymentProperties;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.order.dto.OrderMode;
import cn.itcast.nems.order.dto.OrderPaymentFlowDTO;
import cn.itcast.nems.order.dto.OrderPaymentResultDTO;
import cn.itcast.nems.order.service.OrderService;
import cn.itcast.nems.payment.constant.PayResult;
import cn.itcast.nems.payment.converter.OrderPaymentConerter;
import cn.itcast.nems.payment.dto.PaymentCallbackDTO;
import cn.itcast.nems.payment.dto.PaymentDTO;
import cn.itcast.nems.payment.dto.StudentPaidDTO;
import cn.itcast.nems.payment.dto.WaitPaymentDTO;
import cn.itcast.nems.payment.service.PaymentService;
import cn.itcast.nems.payment.util.RSA2ForMchtUtils;
import cn.itcast.nems.studentrecord.api.StudentRecordPaidApi;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecord;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecordPayment;
import cn.itcast.nems.studentrecord.dto.StudentRecordDTO;
import cn.itcast.nems.studentrecord.dto.StudentRecordPaidDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordCurrentPaymentDTO;
import cn.itcast.nems.studentrecord.dto.studentrecord.StudentRecordPaymentDTO;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordPaymentStateEnum;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordPaymentService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 支付相关业务逻辑
 *
 * @author ChangLu.Gao
 * @version v1.0
 * @program nems-server
 * @description
 * @date 2023/11/17 14:28
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class PaymentServiceImpl implements PaymentService {

    private final StudentRecordService studentRecordService;

    private final StudentRecordPaymentService studentRecordPaymentService;

    private final PaymentProperties paymentProperties;

    private final StudentRecordPaidApi studentRecordPaidApi;

    private final OrderService orderService;

    private static final OkHttpClient httpClient = new OkHttpClient.Builder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS).build();

    @Override
    public PaymentDTO payLink(@NonNull String uniqueCode, @NonNull String ip) {
        StudentRecordDTO studentRecordDTO = studentRecordService.queryByUniqueCode(uniqueCode);
        StudentRecordCurrentPaymentDTO studentRecordCurrentPaymentDTO = this.studentRecordPaymentService.queryPartialPaymentByStudentRecordId(studentRecordDTO.getId());
        if (studentRecordCurrentPaymentDTO.getUnPaidAmount().compareTo(BigDecimal.ZERO) <= 0) {
            List<StudentRecordPaymentDTO> pendingPaymentRecords = this.studentRecordPaymentService.queryByStudentRecordIdAndStatus(studentRecordDTO.getId(), StudentRecordPaymentStateEnum.PAID);
            if (CollectionUtils.isEmpty(pendingPaymentRecords)) {
                return new PaymentDTO(null, PayResult.NOT_REQUIRED, studentRecordDTO.getClazzName());
            } else {
                return new PaymentDTO(pendingPaymentRecords.get(0).getPaymentUrl(), PayResult.FINISH, studentRecordDTO.getClazzName());
            }
        }
        // 所有待支付记录
        List<StudentRecordPaymentDTO> pendingPaymentRecords = this.studentRecordPaymentService.queryByStudentRecordIdAndStatus(studentRecordDTO.getId(), StudentRecordPaymentStateEnum.PENDING);
        // 本次待支付记录
        StudentRecordPaymentDTO currentNeedPayRecord;
        if (CollectionUtils.isEmpty(pendingPaymentRecords)) {
            currentNeedPayRecord = this.studentRecordPaymentService.createNewPendingRecord(studentRecordDTO.getId(), studentRecordCurrentPaymentDTO.getUnPaidAmount());
        } else {
            currentNeedPayRecord = pendingPaymentRecords.get(0);
        }
        if (StringUtils.isNotBlank(currentNeedPayRecord.getPaymentUrl()) && StringUtils.isNotBlank(currentNeedPayRecord.getMerchantOrderNo())) {
            return new PaymentDTO(currentNeedPayRecord.getPaymentUrl(), PayResult.PENDING, studentRecordDTO.getClazzName());
        }
        if (StringUtils.isBlank(currentNeedPayRecord.getMerchantOrderNo())) {
            currentNeedPayRecord.setMerchantOrderNo(UUID.randomUUID().toString().replaceAll("-", ""));
        }
        String payUrl = this.getPayUrlFromPayCenter(this.buildWaitPaymentDTO(currentNeedPayRecord, studentRecordDTO, ip));
        currentNeedPayRecord.setPaymentUrl(payUrl);
        this.studentRecordPaymentService.updateById(CustomBeanUtil.copyProperties(currentNeedPayRecord, new StudentRecordPayment()));
        return new PaymentDTO(payUrl, PayResult.PENDING, studentRecordDTO.getClazzName());
    }

    /**
     * 构建待支付订单信息
     *
     * @param studentRecordPaymentDTO 本次支付信息
     * @param studentRecordDTO        学籍信息
     * @param ip 客户端IP
     * @return 待支付订单信息
     */
    private WaitPaymentDTO buildWaitPaymentDTO(@NonNull StudentRecordPaymentDTO studentRecordPaymentDTO,
                                               @NonNull StudentRecordDTO studentRecordDTO,
                                               @NonNull String ip) {
        WaitPaymentDTO waitPaymentDTO = new WaitPaymentDTO();
        waitPaymentDTO.setOrderNo(studentRecordPaymentDTO.getMerchantOrderNo());
        waitPaymentDTO.setOrderDesc(studentRecordDTO.getClazzName() + " 学员:" + studentRecordDTO.getName());
        waitPaymentDTO.setTotalAmount(
                BigDecimal.valueOf(studentRecordPaymentDTO.getAmount())
                        .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
                        .toString()
        );
        waitPaymentDTO.setOriginalPrice(studentRecordDTO.getActualAmountDue().toString());
        waitPaymentDTO.setProductName(studentRecordDTO.getClazzName());
        waitPaymentDTO.setClientIp(ip);
        waitPaymentDTO.setExtend1(studentRecordDTO.getStudentId().toString());
        waitPaymentDTO.setSubjectName(studentRecordDTO.getSubjectName());
        waitPaymentDTO.setCreatedDatetime(LocalDateTimeUtils.getMilliByTime(studentRecordPaymentDTO.getCreatedDatetime()));
        return waitPaymentDTO;
    }

    /**
     * 请求支付中心获取支付链接
     * @param waitPaymentDTO 待支付订单信息
     * @return 支付链接
     */
    private String getPayUrlFromPayCenter(WaitPaymentDTO waitPaymentDTO) {
        PaymentProperties.ItcastPay payCenterConfig = paymentProperties.getItcastPay();
        SortedMap<String, String> paramsMap = new TreeMap<>();
        paramsMap.put("app_id", Objects.isNull(waitPaymentDTO.getAppId()) ? payCenterConfig.getAppId() : waitPaymentDTO.getAppId());
        paramsMap.put("version", payCenterConfig.getVersion());
        paramsMap.put("charset", "utf-8");
        paramsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
        paramsMap.put("return_url", waitPaymentDTO.getReturnUrl() == null ? payCenterConfig.getReturnUrl() : waitPaymentDTO.getReturnUrl());
        paramsMap.put("notify_url", payCenterConfig.getNotifyUrl());
        //业务参数集合
        SortedMap<String, Object> bizParams = new TreeMap<>();
        bizParams.put("merchant_order_no", waitPaymentDTO.getOrderNo());
        bizParams.put("total_amount", waitPaymentDTO.getTotalAmount());
        bizParams.put("original_price", waitPaymentDTO.getOriginalPrice());
        bizParams.put("currency", waitPaymentDTO.getCurrency());
        bizParams.put("product_name", waitPaymentDTO.getProductName());
        bizParams.put("order_desc", waitPaymentDTO.getOrderDesc());
        bizParams.put("client_type", waitPaymentDTO.getClientType());
        bizParams.put("conn_type", waitPaymentDTO.getConnType());
        bizParams.put("incomeExpensesType", waitPaymentDTO.getIncomeExpensesType());

        if ("DIRECT".equals(waitPaymentDTO.getConnType()))
            bizParams.put("payment_method", waitPaymentDTO.getPaymentMethod());

        bizParams.put("client_ip", waitPaymentDTO.getClientIp());

        if (waitPaymentDTO.getExtend1() != null)
            bizParams.put("extend1", waitPaymentDTO.getExtend1());

        //业务参数集合
        SortedMap<String, Object> extendParams = new TreeMap<>();

        extendParams.put("skms", waitPaymentDTO.getFirstOrgCode());
        extendParams.put("agent_id", waitPaymentDTO.getSecondaryOrgCode());
        extendParams.put("subbranch_id", waitPaymentDTO.getTertiaryOrgCode());

        if (org.springframework.util.StringUtils.hasText(waitPaymentDTO.getSignUpUrl())) {
            extendParams.put("signupUrl", waitPaymentDTO.getSignUpUrl());
            extendParams.put("createdDatetime", waitPaymentDTO.getCreatedDatetime());
        }

        if (org.springframework.util.StringUtils.hasText(waitPaymentDTO.getSubjectName()))
            extendParams.put("subject", waitPaymentDTO.getSubjectName());

        bizParams.put("passback_param", JSON.toJSONString(extendParams));
        paramsMap.put("biz_params", JSON.toJSONString(bizParams));
        String sign = RSA2ForMchtUtils.RSA2Sign(paramsMap, Objects.isNull(waitPaymentDTO.getPrivateKey()) ? payCenterConfig.getPrivateKey() : waitPaymentDTO.getPrivateKey());
        paramsMap.put("sign", sign);

        log.info("请求支付平台，编码前：{}", paramsMap);

        paramsMap.replaceAll((k, v) -> URLEncoder.encode(v, StandardCharsets.UTF_8));

        String paramsJson = JSON.toJSONString(paramsMap);
        String payResult = null;
        try {
            MediaType JSON = MediaType.parse("application/json; charset=utf-8");
            RequestBody body = RequestBody.create(JSON, paramsJson);
            Request request = new Request.Builder()
                    .url(payCenterConfig.getDomain())
                    .post(body)
                    .build();
            payResult = this.sendHttpRequest(request);
            log.info("支付平台响应结果:{}", payResult);
        } catch (Exception e) {
            log.error("调用支付平台创建订单异常,请求报文:{},异常信息", paramsJson, e);
            BizExceptionProducer.throwServerError(BizStatusEnum.INTERNAL_SERVER_ERROR.toString(), "创建预支付订单错误!");
        }
        if (StringUtils.isBlank(payResult)) {
            BizExceptionProducer.throwServerError(BizStatusEnum.INTERNAL_SERVER_ERROR.toString(), "创建预支付订单，支付平台响应为空");

        }
        JSONObject payRelJsonObj = JSON.parseObject(payResult);
        SortedMap<String, String> payRelParamsMap = new TreeMap<>();
        String payRelCode = payRelJsonObj.getString("code");
        String payRelMsg = payRelJsonObj.getString("msg");
        String payRelTimestamp = payRelJsonObj.getString("timestamp");
        String payRelSign = payRelJsonObj.getString("sign");
        payRelParamsMap.put("code", payRelCode);
        payRelParamsMap.put("msg", payRelMsg);
        payRelParamsMap.put("timestamp", payRelTimestamp);
        payRelParamsMap.put("sign", payRelSign);

        String payRelBiz_params = payRelJsonObj.getString("biz_params");

        JSONObject payRelBizParams = JSON.parseObject(payRelBiz_params);
        SortedMap<String, Object> payRelBizParamsMap = new TreeMap<>();
        payRelBizParamsMap.put("merchant_order_no", payRelBizParams.getString("merchant_order_no"));
        payRelBizParamsMap.put("redirect_url", payRelBizParams.getString("redirect_url"));
        payRelParamsMap.put("biz_params", JSON.toJSONString(payRelBizParamsMap));

        boolean signStatus = RSA2ForMchtUtils.rsa2Check(payRelParamsMap, Objects.isNull(waitPaymentDTO.getPublicKey()) ? payCenterConfig.getPublicKey() : waitPaymentDTO.getPublicKey());

        if (!signStatus) {
            log.info("请求支付平台返回签名验证失败:{}", payRelSign);
            BizExceptionProducer.throwServerError(BizStatusEnum.INTERNAL_SERVER_ERROR.toString(), "请求支付平台返回签名验证失败");

        }
        String payRelRedirectUrl = payRelBizParams.getString("redirect_url");
        if (null == payRelCode || "".equals(payRelCode) || null == payRelRedirectUrl || "".equals(payRelRedirectUrl)) {
            BizExceptionProducer.throwServerError(BizStatusEnum.INTERNAL_SERVER_ERROR.toString(), "创建预支付订单失败");
        }
        return payRelRedirectUrl;
    }

    /**
     * 发送http请求
     */
    private String sendHttpRequest(Request request) {
        try (Response response = httpClient.newCall(request).execute()) {
            final ResponseBody body = response.body();
            return body != null ? body.string() : "";
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callback(PaymentCallbackDTO paymentCallbackDTO) {
        log.info("[支付回调]进入业务逻辑时的入参:{}", paymentCallbackDTO);
        StudentRecordPaymentDTO studentRecordPaymentDTO = this.studentRecordPaymentService
                .queryByMerchantOrderNo(paymentCallbackDTO.getBizDto().getMerchantOrderNo());
        String studentRecordId = studentRecordPaymentDTO.getStudentRecordId();
        if (StudentRecordPaymentStateEnum.PAID.equals(studentRecordPaymentDTO.getState())) {
            log.info("[支付回调]根据商户订单号:{}获取的学籍支付记录显示为:{}, 属于支付成功(重试), 流程终止", studentRecordPaymentDTO.getMerchantOrderNo(),
                    StudentRecordPaymentStateEnum.PAID.getDesc());
            return;
        }
        if (!StudentRecordPaymentStateEnum.PENDING.equals(studentRecordPaymentDTO.getState())) {
            this.studentRecordPaymentService.cancelOtherPendingRecordByStudentRecordId(studentRecordId);
        }
        if (paymentCallbackDTO.getBizDto() != null && paymentCallbackDTO.getBizDto().getTotalAmount() != null) {
            studentRecordPaymentDTO.setAmount(
                    paymentCallbackDTO.getBizDto().getSubOrderDto().getPayAmount()
                            .multiply(BigDecimal.valueOf(100))
                            .intValue()
            );
        }
        studentRecordPaymentDTO.setState(StudentRecordPaymentStateEnum.PAID);
        StudentRecordPayment record = CustomBeanUtil.copyProperties(studentRecordPaymentDTO, new StudentRecordPayment());
        record.setState(studentRecordPaymentDTO.getState().name());
        this.studentRecordPaymentService.updateById(record);
        List<String> orderIds = studentRecordService.queryOrderIdsByStudentRecordId(studentRecordId);
        if (CollectionUtils.isNotEmpty(orderIds)) {
            this.orderService.paymentSuccess(this.buildOrderPaymentResultDTO(paymentCallbackDTO, orderIds));
        }else {
            this.studentRecordPaidApi.studentRecordPaid(this.buildStudentRecordPaidDTO(paymentCallbackDTO, studentRecordId));
        }
    }

    @Override
    @Transactional
    public void studentPaid(StudentPaidDTO studentPaidDTO) {
        final int studentId = studentPaidDTO.getStudentId();
        final int clazzId = studentPaidDTO.getClazzId();
        final StudentRecord studentRecord = this.studentRecordService.findStudentRecord(studentId, clazzId);
        if (studentRecord == null) {
            log.info("学员ID: {} 班级ID: {} 没有找到对应的学籍", studentId, clazzId);
        } else {
            final String orderId = this.studentRecordService.findOrderId(studentRecord.getId());
            if (StringUtils.isNotBlank(orderId)) {
                final OrderMode orderMode = this.orderService.findById(orderId);
                final OrderPaymentResultDTO orderPaid = OrderPaymentConerter.conert2OrderPaymentResult(studentPaidDTO, orderMode);
                this.orderService.paymentSuccess(orderPaid);
            } else {
                final StudentRecordPaidDTO srPaid = OrderPaymentConerter.conert2StudentRecordPaid(studentPaidDTO, studentRecord.getId());
                this.studentRecordPaidApi.studentRecordPaid(srPaid);
            }
        }
    }

    /**
     * 构建学籍支付成功参数
     *
     * @param paymentCallbackDTO 支付回调参数
     * @param studentRecordId 学籍ID
     * @return cn.itcast.nems.studentrecord.dto.change.StudentRecordCreateDTO
     * @author 高昌露
     * @since 2023/11/23 18:16
     */
    private StudentRecordPaidDTO buildStudentRecordPaidDTO(PaymentCallbackDTO paymentCallbackDTO, String studentRecordId) {
        var dto = new StudentRecordPaidDTO();
        dto.setStudentRecordId(studentRecordId);
        dto.setBizDate(LocalDateTimeUtils.parse(paymentCallbackDTO.getBizDto().getSubOrderDto().getPaymentTime()));
        dto.setPaymentType("ONLINE_PAY");
        dto.setPaymentNo(paymentCallbackDTO.getBizDto().getSubOrderDto().getSubOrderNo());
        dto.setAmount(paymentCallbackDTO.getBizDto().getSubOrderDto().getPayAmount().multiply(BigDecimal.valueOf(100)).intValue());
        dto.setPaymentMethodCode(paymentCallbackDTO.getBizDto().getSubOrderDto().getPayMethodCode());
        dto.setPaymentMethodName(paymentCallbackDTO.getBizDto().getSubOrderDto().getPayMethodName());
        // todo: paymentAccountCode 及 paymentAccountName 尚未配置
        return dto;
    }

    /**
     * 构建订单支付成功参数
     *
     * @param paymentCallbackDTO 支付回调参数
     * @return cn.itcast.nems.order.dto.OrderPaymentResultDTO
     * @author 高昌露
     * @since 2023/11/24 9:35
     */
    private OrderPaymentResultDTO buildOrderPaymentResultDTO(@NonNull PaymentCallbackDTO paymentCallbackDTO,
                                                             @NonNull List<String> orderIds) {
        var dto = new OrderPaymentResultDTO();
        String orderId = orderIds.get(0);
        var orderMode = this.orderService.findById(orderId);
        if (null != orderMode) {
            dto.setOrderNo(orderMode.getOrderNo());
            dto.setBizOrderNo(orderMode.getBizOrderNo());
        }
        OrderPaymentFlowDTO orderPaymentFlowDTO = new OrderPaymentFlowDTO();
        orderPaymentFlowDTO.setJournalType("ONLINE_PAY");
        orderPaymentFlowDTO.setJournalNo(paymentCallbackDTO.getBizDto().getSubOrderDto().getSubOrderNo());
        orderPaymentFlowDTO.setAmount(paymentCallbackDTO.getBizDto().getSubOrderDto().getPayAmount().multiply(BigDecimal.valueOf(100)).intValue());
        orderPaymentFlowDTO.setBizDate(LocalDateTimeUtils.parse(paymentCallbackDTO.getBizDto().getSubOrderDto().getPaymentTime()));
        orderPaymentFlowDTO.setPaymentMethodCode(paymentCallbackDTO.getBizDto().getSubOrderDto().getPayMethodCode());
        orderPaymentFlowDTO.setPaymentMethodName(paymentCallbackDTO.getBizDto().getSubOrderDto().getPayMethodName());
        // todo: paymentAccountCode 及 paymentAccountName 及 remark 尚未配置
        dto.setFlows(List.of(orderPaymentFlowDTO));
        return dto;
    }
}
